﻿using System;
using System.Timers;
using mki.Backup.Helper;
using mki.Backup.Helper.Extensions;
using System.Net.Mail;
using System.IO;
using mki.Backup.Objects;

namespace mki.Backup.Interfaces
{
	/// <summary>
	/// Service class
	/// </summary>
	public class RunJobService
	{
		#region Fields (3)

		// ReadOnly Fields (2) 

		private readonly BackupJobSection jobConfig;

		private readonly Timer jobTimer;
		// Fields (1) 

		private Boolean parentIsStopping = false;

		#endregion Fields

		#region Constructors (1)

		/// <summary>
		/// Initializes a new instance of the <see cref="RunJobService"/> class.
		/// </summary>
		public RunJobService()
		{
			// load config
			try
			{
				this.jobConfig = BackupConfiguration.Settings.BackupJobsCollection;
			}
			catch (Exception ex)
			{
				Logging.WriteError("while loading configuration: {0}\n{1}", ex.Message, ex.StackTrace);
			}

			// list found jobs
			foreach (BackupJobElement backupJob in this.jobConfig)
			{
				Logging.WriteInformation("found {0} backupjob '{1}' which is scheduled on {2}", backupJob.Enabled ? "enabled" : "disabled", backupJob.Name, backupJob.StartJobAt);
			}

			// init timer
			this.jobTimer = new Timer(syncedToFullMinuteInterval) { AutoReset = false };
			this.jobTimer.Elapsed += jobTimerElapsed;

			// and start it
			this.jobTimer.Start();
		}

		#endregion Constructors

		#region Properties (1)

		// Private Properties (1) 

		/// <summary>
		/// Gets the synced to full minute interval.
		/// </summary>
		/// <value>The synced to full minute interval.</value>
		private static Double syncedToFullMinuteInterval
		{
			get
			{
				var currentDateTime = DateTime.Now;
				return Math.Ceiling((TimeSpan.FromMinutes(Math.Ceiling(currentDateTime.TimeOfDay.TotalMinutes)) - currentDateTime.TimeOfDay).TotalMilliseconds + 100);
			}
		}

		#endregion Properties

		#region Methods (9)

		// Public Methods (1) 

		/// <summary>
		/// Stops this instance.
		/// </summary>
		public void Stop()
		{
			Logging.WriteVerbose("Attempting to stop jobs");
			this.parentIsStopping = true;
			this.jobTimer.Stop();
		}
		// Private Static Methods (5) 

		/// <summary>
		/// Executes the backup jobs.
		/// </summary>
		/// <param name="backupJob">The backup job.</param>
		private static void executeBackupJobs(BackupJobElement backupJob)
		{
			foreach (DBBackupElement dbBackup in backupJob.DBBackupJobsCollection)
			{
				try
				{
					var fileName = String.Empty;
					if (DBBackupJob.RunJob(dbBackup, out fileName))
					{
						Logging.WriteInformation("db backup job for database '{0}' completed successfully", dbBackup.DatabaseName);
					}
					else
					{
						throw new ApplicationException("Error while executing db backupjob");
					}
				}
				catch (Exception ex)
				{
					if (backupJob.ContinueOnError) { Logging.WriteError("while running db backup job '{0}': {1}\n{2}", dbBackup.Name, ex.Message, ex.StackTrace); }
					else { throw new ApplicationException(String.Format("Error while running db backup job '{0}': {1}\n{2}", dbBackup.Name, ex.Message, ex.StackTrace)); }
				}
			}
		}

		/// <summary>
		/// Executes the FTP jobs.
		/// </summary>
		/// <param name="backupJob">The backup job.</param>
		private static void executeFTPJobs(BackupJobElement backupJob)
		{
			foreach (FTPJobElement ftpJob in backupJob.FTPJobsCollection)
			{
				try
				{
					if (FTPJob.RunJob(ftpJob))
					{
						Logging.WriteInformation("run ftp job '{0}' completed successfully", ftpJob.Name);
					}
					else
					{
						throw new ApplicationException("Error while executing ftpJob");
					}
				}
				catch (Exception ex)
				{
					if (backupJob.ContinueOnError) { Logging.WriteError("while running ftp job '{0}': {1}\n{2}", ftpJob.Name, ex.Message, ex.StackTrace); }
					else { throw new ApplicationException(String.Format("Error while running ftp job '{0}': {1}\n{2}", ftpJob.Name, ex.Message, ex.StackTrace)); }
				}
			}
		}

		/// <summary>
		/// Executes the run process jobs.
		/// </summary>
		/// <param name="backupJob">The backup job.</param>
		private static void executeRunProcessJobs(BackupJobElement backupJob)
		{
			foreach (RunProcessElement runProcessJob in backupJob.RunProcessJobsCollection)
			{
				try
				{
					if (RunProcessJob.RunJob(runProcessJob))
					{
						Logging.WriteInformation("run process job '{0}' completed successfully", runProcessJob.Name);
					}
					else
					{
						throw new ApplicationException("Error while executing runProcess");
					}
				}
				catch (Exception ex)
				{
					if (backupJob.ContinueOnError) { Logging.WriteError("while running run process job '{0}': {1}\n{2}", runProcessJob.Name, ex.Message, ex.StackTrace); }
					else { throw new ApplicationException(String.Format("Error while running run process job '{0}': {1}\n{2}", runProcessJob.Name, ex.Message, ex.StackTrace)); }
				}
			}
		}

		/// <summary>
		/// Executes the zip jobs.
		/// </summary>
		/// <param name="backupJob">The backup job.</param>
		private static void executeZipJobs(BackupJobElement backupJob)
		{
			foreach (ZipJobElement runZipJob in backupJob.ZipJobsCollection)
			{
				try
				{
					if (ZipJob.RunJob(runZipJob))
					{
						Logging.WriteInformation("run zip job '{0}' completed successfully", runZipJob.Name);
					}
					else
					{
						throw new ApplicationException("Error while executing zipJob");
					}
				}
				catch (Exception ex)
				{
					if (backupJob.ContinueOnError) { Logging.WriteError("while running zip job '{0}': {1}\n{2}", runZipJob.Name, ex.Message, ex.StackTrace); }
					else { throw new ApplicationException(String.Format("Error while running zip job '{0}': {1}\n{2}", runZipJob.Name, ex.Message, ex.StackTrace)); }
				}
			}
		}

		/// <summary>
		/// Sends the result mail.
		/// </summary>
		/// <param name="backupJob">The backup job.</param>
		private static void sendResultMail(BackupJobElement backupJob)
		{
			if (!String.IsNullOrWhiteSpace(backupJob.SendResultMailTo) && backupJob.SendResultMailTo.IsValidEmailAddress())
			{
				try
				{
					using (var client = new SmtpClient())
					{
						var mail = new MailMessage(new MailMessage().From.Address, backupJob.SendResultMailTo.Trim());
						Logging.WriteVerbose("Sending job result mail from '{0}' to '{1}'", mail.From, mail.To);
						mail.Subject = String.Format("BackupJob '{0}' on host '{1}' {2}", backupJob.Name, Environment.MachineName, backupJob.Successfull ? "completed successfully" : "failed");
						mail.Body = String.Format("BackupJob '{0}' on host '{1}' {2}\n\nStarted on: {3}\nEnded on: {4}",
													backupJob.Name, Environment.MachineName,
													backupJob.Successfull ? "completed successfully" : "failed",
													new DateTime(backupJob.TimeStart.Ticks), new DateTime(backupJob.TimeEnd.Ticks));

						if (backupJob.AttachLogFile && !String.IsNullOrWhiteSpace(backupJob.LogFilePath))
						{
							Logging.WriteInformation("Trying to attach {0}", backupJob.LogFilePath);
							if (File.Exists(backupJob.LogFilePath))
							{
								try
								{
									var fileStream = new FileStream(backupJob.LogFilePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
									mail.Attachments.Add(new Attachment(fileStream, Path.GetFileName(backupJob.LogFilePath)));
								}
								catch (Exception ex)
								{
									Logging.WriteInformation("Failed to attach logfile '{0}': {1}\n{2}", backupJob.LogFilePath, ex.Message, ex.StackTrace);
								}
							}
						}

						client.Send(mail);
					}
				}
				catch (Exception ex)
				{
					Logging.WriteError("while sending email to '{0}': {1}\n{2}", backupJob.SendResultMailTo, ex.Message, ex.StackTrace);
				}
			}
		}
		// Private Methods (3) 

		/// <summary>
		/// Checks the and execute jobs.
		/// </summary>
		private void checkAndExecuteJobs()
		{
			if (this.jobConfig == null) { return; }

			Logging.WriteInformation("Checking for jobs");

			// check if a job is present
			foreach (BackupJobElement backupJob in this.jobConfig)
			{
				if (!backupJob.Enabled) { continue; }

				backupJob.Successfull = false;
				var startTime = DateTime.Parse(backupJob.StartJobAt);
				if (startTime.Hour == DateTime.Now.Hour && startTime.Minute == DateTime.Now.Minute)
				{
					// stop timer so we wont check for jobs if we're currently execute one
					this.jobTimer.Stop();

					// ToDo: run actions in given oder from config
					Logging.WriteInformation("Found job '{0}' which starts on '{1}'", backupJob.Name, backupJob.StartJobAt);
					try
					{
						// create custom logfile for this backup job?
						if (backupJob.AttachLogFile)
						{
							var currentDirectory = Path.GetDirectoryName(Environment.CommandLine.Remove(Environment.CommandLine.Length - 2, 2).Remove(0, 1));
							backupJob.LogFilePath = Path.Combine(currentDirectory, Logging.LogPath, String.Format("BackupJob_{0}_{1}_{2}.log", backupJob.Name, DateTime.Now.ToString("yyyyMMdd"), backupJob.StartJobAt.Replace(":", String.Empty)));
							Logging.WriteInformation("Switching logfile to '{0}'", backupJob.LogFilePath);
							Logging.LogFilePath = backupJob.LogFilePath;
						}

						backupJob.TimeStart = new TimeSpan(DateTime.Now.Ticks);

						// database backup job
						executeBackupJobs(backupJob);

						// run process job
						executeRunProcessJobs(backupJob);

						// run zip job
						executeZipJobs(backupJob);

						// ftp job
						executeFTPJobs(backupJob);

						// remember job results
						backupJob.Successfull = true;
						backupJob.TimeEnd = new TimeSpan(DateTime.Now.Ticks);

						Logging.WriteInformation("Job '{0}' completed in {1} seconds!", backupJob.Name, backupJob.TimeForExecution);
					}
					catch (Exception ex)
					{
						Logging.WriteError("Backup job failed: {0}\n{1}", ex.Message, ex.StackTrace);
						backupJob.Successfull = false;
					}

					// switch back to normal log
					Logging.LogFilePath = String.Empty;

					// send job results
					sendResultMail(backupJob);

					Logging.WriteInformation("Finished job '{0}' see '{1}' for details", backupJob.Name, backupJob.LogFilePath);
				}
			}

			this.syncJobTimer();
		}

		/// <summary>
		/// Jobs the timer elapsed.
		/// </summary>
		/// <param name="sender">The sender.</param>
		/// <param name="e">The <see cref="System.Timers.ElapsedEventArgs"/> instance containing the event data.</param>
		private void jobTimerElapsed(object sender, ElapsedEventArgs e)
		{
			// check for jobs
			this.checkAndExecuteJobs();
		}

		/// <summary>
		/// Syncs the job timer.
		/// </summary>
		private void syncJobTimer()
		{	// parent tries to stop?
			if (this.parentIsStopping) { return; }

			// now sync
			if (this.jobTimer.Enabled) { this.jobTimer.Stop(); }
			this.jobTimer.Interval = syncedToFullMinuteInterval;
			this.jobTimer.Start();
		}

		#endregion Methods
	}
}